# Getting Started (HTML5 Proof of Concept)

[Return to UniversalMediaController Home](../index.html)

This is the first working milestone of UniversalMediaController. It covers
native HTML5 `<audio>` and `<video>` only -- see Docs/Supported Players.md
for what is planned later, and Docs/Limitations.md for what is intentionally
not in this milestone.

## Running the examples

The examples use native ES modules and need to be served over HTTP (not
opened directly as `file://`), and the server needs to support HTTP Range
requests for `<video>`/`<audio>` seeking to work (most production servers
do this by default; a bare-bones `python -m http.server` does not). From
the repository root:

```
python Tools/serve-examples.py
```

This is a small dependency-free script (standard library only) that adds
the Range support a bare `http.server` lacks -- see README.md, "Testing the
HTML5 examples" for why that matters.

Then open:

- `http://localhost:8000/Examples/HTML5/video-example.html`
- `http://localhost:8000/Examples/HTML5/audio-example.html`
- `http://localhost:8000/Examples/HTML5/audio-example-untitled.html`
  (demonstrates the low-confidence filename fallback)
- `http://localhost:8000/Examples/HTML5/multiple-controllers-example.html`
  (demonstrates two independent controllers on one page)

## Associating a controller with a media element

```js
import { UniversalMediaController } from "../../Packages/Core/index.js";

const video = document.getElementById("my-video");
new UniversalMediaController(video, {
  mountPoint: document.getElementById("my-video-controls"), // optional, defaults to the media element's parent
  explicitTitle: "Introduction to Open Door Design",        // optional, see below
  skipSeconds: 10,                                           // optional, defaults to 10
});
```

The constructor:

1. Resolves an accessible title for the media (see below).
2. Connects an `HTML5MediaAdapter` to the element (throws if the element is
   not an `HTMLMediaElement`, or if it is already connected to another
   UniversalMediaController instance).
3. Builds the accessible controls and appends them to `mountPoint`.
4. Wires everything together and keeps the controls synchronized with the
   media element's actual state.

## Using a non-HTML5 adapter (external-player readiness)

`mountPoint`, `explicitTitle`, and `skipSeconds` are not the only option:
`adapter` lets you supply a pre-built adapter instance instead of relying
on the default HTML5 detection:

```js
new UniversalMediaController(someElement, {
  adapter: someAdapterInstance, // must implement MediaAdapterContract
  explicitTitle: "Title",
});
```

When `adapter` is supplied, `someElement` no longer needs to be an
HTMLMediaElement -- the adapter's own `connect()` method decides what it
can connect to. This is the extension point a future external-player
adapter (Vimeo, YouTube, etc.) will use; no such adapter exists yet in this
milestone. See Docs/Adapter Contract.md, "External player readiness."

## Providing an explicit title

Pass `explicitTitle` in the options object. This is the highest-priority
title source and always wins over anything inferred from the page.

## How title inference works

When `explicitTitle` is not supplied, UniversalMediaController looks for a
title in this order (see Docs/Architecture.md for the full rationale):

1. `explicitTitle` option
2. `aria-labelledby` on the media element
3. `aria-label` on the media element
4. The media element's `title` attribute
5. A `<figcaption>` inside an ancestor `<figure>`, or the nearest preceding
   `h2`-`h6` within the media element's immediate parent (an `<h1>` is never
   used, since a page should have exactly one `<h1>` identifying the page
   itself, not a specific media item)
6. A cleaned-up version of the media filename, as a low-confidence fallback

If nothing at all can be found, a generic placeholder title is used and a
warning is logged to the console. This is intentional: UniversalMediaController
never silently ships an unlabeled controller (see Docs/Universal Media
Controller Design Philosophy.md, "No unidentified media").

## How the first-control naming behavior works

The control order is accessibility-first (see Docs/Version 1
Requirements.md, "Accessibility-first control order"): Audio Description,
Captions, and Transcript come before any playback control. The first
focusable control is now Audio Description, and it -- not Play/Pause --
carries the resolved title in its accessible name:

- `Audio Description for "Title", not available` when the adapter does not
  support audio description (the HTML5 adapter never does in this
  milestone -- see Docs/Limitations.md)
- `Audio Description for "Title", off` / `..., on` when it is supported

This lets a user who tabs directly to the first control, without first
encountering the named container, still learn what media the whole
controller operates. Every later control (Captions, Transcript, Play/Pause,
Restart, Mute, Volume, Playback speed, etc.) stays concise and does not
repeat the title -- for example `Captions, off` or `Play`, never `Captions
for "Title", off`.

## Capability model and not-available controls

The controller asks the connected adapter what it supports instead of
branching on adapter type:

```js
supportsAudioDescription(), supportsCaptions(), supportsTranscript(),
supportsPlaybackSpeed(), supportsSeeking(), supportsVolume(),
supportsMute(), supportsRestart(), supportsBookmarks(), supportsChapters()
```

A control whose capability is unsupported (or, for Captions specifically,
supported by the adapter but not present on the current media item) is
never removed from the page, and is never modeled as disabled. It is
built and tested as an ordinary, fully enabled button, since it always
does something useful when activated (it explains that the feature is
not available). No `disabled` attribute, `aria-disabled`, or
`tabindex="-1"` is ever set -- earlier attempts that used
`aria-disabled="true"` (while keeping the control otherwise focusable)
turned out to be unreliable across JAWS and NVDA in real testing; see
Docs/Limitations.md, "Issue 1", for that history. The control's
accessible name states "not available" directly (e.g. `Captions, not
available`), and activating it still fires normally -- the controller
announces a concise explanation (e.g. "Captions are not available for
'Title'.") instead of performing the action.

## Centralized state

`Packages/Core/controller-state.js` defines a small state machine
(`ControllerState`: `loading`, `ready`, `playing`, `paused`, `seeking`,
`ended`, `error`) that the core controller reacts to in one place
(`_handleStateChange`) rather than letting each control independently
decide when to update itself or announce something. Adapter events
translate into state transitions; only a genuine transition (not a
same-state re-entry) notifies listeners, which is what keeps a spurious
native "playing" re-fire after a seek from re-triggering a "Playback
started" announcement.

## Current status

Working HTML5 proof of concept covering the full accessibility-first
control order: Audio Description, Captions, Transcript, Play/Pause,
Restart, Skip backward/forward, Playback speed (0.5x-2x in quarter-step
increments plus normal), Mute, Volume, and Current time/duration. See the
delivery report for exactly what has and has not been manually verified
with real assistive technology.
